home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-12-03 | 27.5 KB | 659 lines | [TEXT/R*ch] |
- [8] How do I write a Control Panel-INIT combination?
-
- System extensions generally have no interface. They do what they do
- quietly and the user doesn't want to hear from them. In many cases
- the user needs to set certain preferences. The natural mechanism for
- this is to couple a control panel with an INIT. The problem of
- course is how does the control panel communicate the changes to the
- INIT. I won't discuss the general mechanisms of control panel
- authoring here (see NIM: More Macintosh Toolbox, Chapter 8), but
- there are several mechanisms available for communicating between
- extensions and control panels.
-
- The simplest mechanism and one that will work in the vast majority of
- cases is for the extension to install a Gestalt selector. This
- selector returns the address of a block of memory that holds
- variables that control the actions of the extension. The control
- panel calls the Gestalt selector, retrieves the address of the memory
- block, and alters the values stored in the block as needed. In some
- cases the memory block can contain a function pointer to a function
- in the extension that needs to be called from the control panel.
- Here is some sample code:
-
- typedef struct CommonInfo{
- void (*ResetINIT) (void);//function pointer to reset
- //func
- Boolean On;
- };
-
- #define kSignature 'BLAH' //Should be the sig of the INIT
-
- static CommonInfo gInfo;
-
- /**InstallGestaltSelector****************************************/
- //In the INIT; runs at INIT time
- OSErr
- InstallGestaltSelector(void)
- {
- OSErr err;
-
- err = NewGestalt( kSignature, OurSelector );
-
- if ( err == noErr )
- {
- gInfo.ResetINIT = (void *) ResetFunction;
- gInfo.On = TRUE;
- }
-
- return err;
- }
-
- /**OurSelector**************************************************/
- //In the INIT
- pascal OSErr
- OurSelector( OSType theSelector, long *theResponse )
- {
- SetUpA4();
-
- *theResponse = (long) &gInfo;
-
- RestoreA4();
-
- return noErr;
- }
-
- /**ResetFunction**************************************************/
- //In the INIT
- void
- ResetFunction(void)
- {
- SetUpA4();
-
- //Do something here
-
- RestoreA4();
- }
-
- /**Close********************************************************/
- //In the Control Panel
- void
- Close( Boolean IsOn )
- {
- long result;
- CommonInfo *Info;
-
- //Get address of globals struct from init
- err = Gestalt( kSignature, &result );
- if ( err == noErr )
- {
- Info = (GlobalsType *) result;
- Info->On = IsOn; //Reset OnOff Boolean
- ( * (*Info).ResetINIT) (); //Jump to INIT
- }
- }
-
- If it is possible for an error to occur that prevents the extension
- from resetting itself the ResetFunction should return an error code
- and the control panel should display an alert indicating the problem.
-
- Two additional methods are sometimes used to accomplish communication
- between an extension and a control panel:
-
- The first of these is to write a driver that is installed by the
- extension. The driver holds global variables and will return the
- address of the block of memory holding these variables in response to
- i/o, status, or control calls to the driver. Sample code showing how
- to do this is available in a package called driver-22 written by Pete
- Resnick to be found at:
- ftp://sumex-aim.stanford.edu/info-mac/dev/src/driver-22-c.hqx.gz
-
- The second additional method is to use the PPC toolbox for direct
- communication. There is sample code demonstrating this at:
- ftp://ftp.apple.com/dts/mac/sc/7.0.samples/init-cdev.hqx.
-
- --
- [9] How do I capture keystrokes?
-
- This is accomplished by writing a jGNEFilter function. jGNEFilter
- functions are called from GetNextEvent and WaitNextEvent just before
- those traps return to an application. They are passed a pointer to
- the event record that will be returned to the application. In order
- to capture keystrokes the jGNEFilter would simply check the what
- field of the event record looking for keyboard events and would
- extract the information from the message field if one were found.
-
- The jGNEFilter mechanism is very powerful and is one way that
- screensavers can be implemented. The filter would save the time of
- any keyboard events and would compare the location of the mouse
- against its previous location on null events. If the preset time had
- elapsed during which no keyboard events or mouse movement had
- occurred then the screen saver would activate. A more complete
- discussion of the jGNEFilter is in the next section.
-
- If you are thinking of patching WaitNextEvent or PostEvent in order
- to capture keystrokes or other events, don't. Use a jGNEFilter
- instead. It's easier, it's compatible. It's even documented. See
- the Tech Note 'GetNextEvent; Blinking Apple Menu' (#85).
-
- ftp://ftp.apple.com/dts/mac/tn/toolbox.tb/tb-11-getnextevent.hqx
-
- --
- [10] How can my extension get time periodically?
-
- Installing a jGNEFilter is one method of obtaining periodic time.
- Since the jGNEFilter mechanism is dependent on the event processing
- mechanism, one problem is that no events may be posted if the mouse
- is held down for an extended time.
-
- If your INIT only needs to get time every once in a while then I
- recommend that it only do its thing on null events. On other events
- it should just return. This will have the least impact on the
- machine's performance.
-
- Remember that your jGNEFilter will be called for EVERY event on the
- machine. Do not do a lot of processing on every event or you will be
- slowing down the machine needlessly. Another way to reduce the
- frequency of your extension's processing is to use a simple timer,
- based on TickCount(). In this way your processing is only done, say,
- every 30 or 60 ticks, on null events of course.
-
- Sample code demonstrating jGNEFilters can be found in a package
- called jGNE Helper by Pete Gontier in the alt.sources.mac archive at:
- ftp://ftpbio.bgsu.edu/ftp/pub/alt.sources.mac/vol-
- 01/jgnehelper.cpt.hqx.
-
- Another sample in MPW assembler is at
- ftp://ftp.apple.com/dts/mac/sc/snippets/toolbox/jgnefilter.hqx.
-
- Here's another example that works in Think C:
-
- static ProcPtr gOldGNEFilter;
-
- /****InstallFilter***********************************************/
- //Run this at INIT time
- void
- InstallGNEFilter (void)
- {
- /*Save the ProcPtr to the previous jGNEFilter and insert ours*/
- gOldGNEFilter = JGNEFilter;
- JGNEFilter = (ProcPtr) StripAddress( FilterProc );
-
- }
-
- /****FilterProc**************************************************/
-
- void
- FilterProc(void)
- {
- EventRecord *theEvent;
- long SaveD0;
-
- theEvent = GetA1(); //Move the eventPtr to a variable
- SaveD0 = GetD0(); //Preserve D0
-
- SetUpA4();
-
- switch ( (*theEvent).what ) {
- case nullEvent:
- //Do our thing
- break;
-
- case keyDown:
- //Do something else
- break;
- }
-
- //Execute the previous jGNEFilter
- SetA1( theEvent ); //Restore A1 for the next jGNEFiler
- SetD0( SaveD0); //Restore D0
- SetA0( gOldGNEFilter ); //Put next jGNEFilter in A0
-
- RestoreA4();
-
- asm{
- Unlk A6
- Move.W D0, 4(A7) ;Set Function result on the stack
- JMP (A0) ;Jump to the next jGNEFilter
- }
-
- }
-
- Since a jGNEFilter has access to the actual event record that will be
- returned to the application, the filter can alter the event. The
- most common thing to do is to 'cancel' an event by changing it to a
- null event (Ex. theEvent->what = nullEvent). In this case it should
- also set the value in register D0 to False, or zero.
-
- Here are some additional methods for an extension to get time
- periodically:
-
- If your extension needs more frequent or regular time then can be
- provided by a jGNEFilter then you can install a VBL task or a time
- manager task. Since these run at interrupt time they cannot do
- anything that could move or purge memory. Other methods include
- patching a trap that is called frequently, like SetPort.
-
- A faceless Notification Manager request is another method to get
- time. This is a request that has all the fields in the notification
- record set to NULL except the nmResp field that holds the address of
- your routine to be executed. Your notification response routine will
- be called soon and will be able to move memory. If necessary the
- routine can reinstall itself. This technique is useful for tasks
- that need to be executed once or intermittently. For those tasks
- that need to be executed regularly use one of the other techniques.
-
- You could of course have a faceless NM request that is installed by a
- VBL task or a time manager task.
-
- Use of a faceless background application in concert with an extension
- is yet another method to get time. The fba would do its work on its
- null events.
-
- --
- [11] How should an INIT manage memory?
-
- In general, at INIT time extensions will want to allocate memory in
- the system heap. If you are allocating pointers or handles, use the
- SYS variants, like NewHandleSys() and NewPtrSys(). It is a common
- error to forget this and then to wonder why the INIT crashes. If you
- call NewHandle at INIT time, the handle will be allocated in the
- temporary heap allocated for your extension. If your INIT attempts
- to use it after INIT time the temporary heap will be long gone, along
- with any handles or pointers that had been allocated in it. Any
- attempt to use handles or pointers that no longer exist are
- predictably unpredictable :-)
-
- NewHandle and NewPtr will allocate their memory blocks in the system
- heap if the zone has been set to the system heap, as shown in the
- next paragraph.
-
- If you need to read in resources you can ensure that they go into the
- system heap with something like the following code:
-
- THz saveZone = GetZone();
- SetZone( SystemZone() );
- //read in resources
- SetZone( saveZone );
-
- You will of course need to detach the resources if they need to
- remain in memory after your extension exits. The resource file
- containing your extension will be closed when your extension exits.
-
- If you need to read resources into memory after INIT time you need to
- decide which heap they should go into, either the application heap or
- the system heap. It's a bit hard to make a specific recommendation
- on this but if the resource is something that the application is
- expecting to be read in then it should go into the application heap.
- This would include things like WIND resources in a trap patch to
- GetNewWindow(). The problem of course is that the application heap
- may not have enough room. However, if the resources are private to
- the extension then they should go into the system heap.
-
- It should go without saying that you should always check the error
- codes on memory allocating calls and resource manager calls. If your
- extension is doing something in response to a user action, say making
- a network connection, then it is appropriate to report such errors.
- In many cases however, your extension will simply do nothing in the
- case of out of memory errors or missing resource errors. It is best
- to attempt to allocate all of these things at INIT time and if
- unsuccessful to bail out then.
-
- As mentioned above, MoveHHi doesn't work in the system heap. If you
- intend to allocate a handle that will remain locked for extended
- periods, then call ResrvMem before allocating and locking the handle.
- This will place the handle low in the system heap and help to prevent
- heap fragmentation.
-
- Many toolbox calls are documented as not moving or purging memory and
- as being safe to call at interrupt time. If you are patching one of
- these traps then you must preserve this property. You are guaranteed
- to cause other software to crash if you don't, and your users will
- hate you (once they figure out that it's you). Be aware that the
- only memory manager routine safe to call under these circumstances is
- BlockMove. Also be aware that it is unsafe to access an unlocked
- handle at interrupt time. It is possible that the memory manager is
- in the midst of moving it from one place to another in the heap, and
- the master pointer may not be updated yet.
-
- --
- [12] How do I get my INIT to turn itself off?
-
- An extension might want to turn itself off at INIT time based on its
- preferences setting, or based on a key being pressed or the mouse
- button being pressed, or due to an error during initialization.
- Extensions usually show an icon with a red X through it in this case.
- An extension might also want to turn itself off temporarily after
- INIT time in response to its Control Panel. For instance GateKeeper
- has an on/off switch that turns off virus checking for a set time.
-
- The strategy for temporarily turning off an extension is simply to
- set a global flag and to check it from within the trap patches or
- other parts of the extension. If the flag is off then the trap patch
- simply executes the previous trap. It is generally unsafe to unpatch
- or patch traps after INIT time from an extension. The reason for
- this is that if another extension patches the same trap after you
- have, then it will be jumping to your patch when it has completed its
- work. If you have removed your patch then this calling chain will be
- disrupted and bad things will happen. Also, the Finder patches
- various traps when it loads, which is after INIT time. Disrupting
- those patches would be a very bad thing.
-
- If an extension determines at INIT time that it isn't going to stay
- around then it shouldn't call DetachResource on itself. It's best
- that your extension determine that anything it's dependent on, such
- as resources, specific system Managers, and sufficient memory, are
- present *before* it starts to patch traps and install drivers,
- jGNEFilters and so on. It would be a very bad idea for an extension
- to not detach itself after it had already patched a trap if the trap
- patch resided in the extension.
-
- Many extensions use a particular key press as a signal to indicate
- that the user wants them not to run. Of course the system uses the
- shift key as a signal not to turn on any extensions so you can't use
- that. Some extensions use the option or command keys for this
- purpose. The problem with using those keys is that every time I
- rebuild the desktop those extensions are needlessly inactivated. I
- recommend that the space bar be used for this purpose. Here's some
- sample code:
-
- Boolean
- SpaceBarIsDown(void)
- {
- KeyMap theKeys;
-
- GetKeys( theKeys );
-
- if ( theKeys[1] & 0x00000200 )//Check for spacebar
- return TRUE;
- else
- return FALSE;
-
- }
-
- --
- [13] How do I get my INIT to show it's icon like all the other cool
- inits do?
-
- This is easy. There is code available that does this for you. Get a
- package written by Jim Walker called ShowIcon7 at:
- ftp://mac.archive.umich.edu/mac/development/source/showicon7.sit.hqx
-
- You use it essentially as a plug-in. Just pass in the icon's
- resource ID and it does everything for you. It also shows how to set
- up an A5 world in an extension. You might also take a look at Dair
- Grant's Extension shell package. This one shows how to do animated
- icons.
-
- If your extension decides that it can't install itself then it passes
- the resource ID of an icon that has a red X through it to the
- showicon7 code resource.
-
- Some extensions include the ShowIcon code within their own code
- resources. This code is only about 1K but it seems pointless to me
- for this code to sit in the system heap when it doesn't have to be.
- Use it as a plug-in for your extensions.
-
- --
- [14] How do I show a dialog from my INIT?
-
- First of all, I hate windows of any kind during the startup process.
- If all you want to do is show an alert then use the Notification
- Manager. Your alert will show up when the Finder starts but the user
- will see it and will get whatever message you need to send.
-
- The problem I have with windows at INIT time is that they slow down
- this process and require user interactivity in a process that
- shouldn't do so. Consider the computer that is on 24 hours a day
- doing something important unattended. The power goes off and when it
- comes back on the machine reboots. The user returns several hours
- later to find that the machine is still in the middle of its startup
- because your alert is waiting for a response. Consider also the
- hapless INIT writer who has to reboot his machine 20 times a day.
- Your INIT will not last long on my, um, his machine.
-
- One additional annoyance is that you need to call InitWindows to show
- your window and this erases all the nice INIT icons on the screen. I
- hate that.
-
- Here are a few possible alternatives.
-
- * Play a sound or use the Speech Manager to communicate the
- information.
-
- * Show a different icon during startup to indicate an error. An icon
- with a red X through it is one way to do this. You could also use
- animated icons.
-
- * If you must put up an alert then use a timer so that the alert goes
- away by itself, even if the OK button isn't clicked.
-
- * If you need to interact with the user to get some information, say
- a password for a network connection, then do this once and save the
- results in a preferences file. Provide a Control Panel to change the
- information. Think of Control Panels as the interface for
- extensions.
-
- * Store descriptions of any errors that occur in a preferences file.
- Have the Control Panel display this information. Remember to clear
- this information on each restart and to indicate to the user by sound
- or icon that an error has occurred.
-
- * If you need to communicate error information to the user after INIT
- time then you should definitely use the Notification Manager. For
- example, MacSLIP uses the NM to show an alert indicating that the
- carrier has been lost.
-
- If you still want to show a dialog at INIT time then you need to set
- up an A5 world first. The ShowIcon7 code mentioned above shows how
- to do this. Also see the Tech Note 'Stand-Alone Code' (#256) for an
- explanation and sample code for this and 'Giving the (Desk)Hook to
- INITs' (247) discusses a bug that can appear when showing windows
- from extensions.
- ftp://ftp.apple.com/dts/mac/tn/operating.system.os/os-02-deskhook-
- and-init.hqx
-
- If you do use the Notification Manager from an extension to indicate
- that the extension couldn't load you should use a self-disposing
- Notification request. There are several strategies for doing this.
- I have some code samples for this that will be (have been?) posted to
- alt.sources.mac soon.
-
- --
- [15] How do I maintain compatibility with future systems?
-
- This is tough, and the short answer is that you probably don't.
- You'll notice that Apple comes out with new versions of its
- extensions with each revision of the system. Apple's extensions also
- eventually disappear as their functionality is rolled into the
- system. In all likelihood you'll have to come out with new versions
- of your extensions as new versions of the system come out as well.
-
- Having said all that, there are things you can do to minimize this
- problem. Here are a few suggestions:
-
- * Minimize your reliance on undocumented features of the system.
-
- * Minimize your reliance on low memory globals. Use the Universal
- Header access 'functions' for accessing the low memory globals if
- necessary.
-
- * Use system features like Gestalt, the Process Manager, and the
- Notification Manager to get information about the system, and to
- communicate with the user.
-
- * Try to patch as few traps as possible and use non-patching methods
- whenever possible (e.g., use a jGNEFilter instead of patching
- WaitNextEvent; the filter is more likely to remain compatible than
- your patch).
-
- * Don't use self-modifying code. Self-modifying code changes the
- instructions from what they were compiled as, to something else, at
- run-time. The classic example is to change the address in a JMP
- instruction at run-time so that it jumps to the address of the
- previous trap. This may seem faster than using a global variable but
- it is only slightly faster. It is harder to write, debug, and
- maintain self-modifying code, and it is definitely more likely to
- break with new system releases. If you do use self-modifying code,
- remember to flush the cache. See the tech note 'Cache As Cache Can'
- (#261) for more information on that subject.
-
- * Use the Universal Headers for writing your extensions. This will
- help to ease the transition when it comes.
-
- --
- [16] Any tips for INIT writing?
-
- You may not want your INIT to actually do anything until after INIT
- time. You can find the end of INIT time if you have a jGNEFilter
- installed when the first null event occurs. The Notification Manager
- doesn't usually start processing requests until INIT time is over so
- posting a faceless notification request is another method to find the
- end of INIT time (probably the best if you don't need a jGNEFilter
- for something else). You can also patch Launch to find the end of
- INIT time but this is trickier.
-
- Unfortunately some extension writers do put up windows during INIT
- time. Because of this it's possible that events will occur before
- INIT time is over. To fail-safe the above approaches you also need
- to check for the presence of the Process Manager with a Gestalt call.
- The Process Manager isn't available until after INIT time. If
- Gestalt reports that the Process Manager is not available then you
- need to reinstall your NM request, or simply wait for another event
- to be reported to your jGNEFilter when the Process Manager is
- available.
-
- Here are some utility routines that can reduce the need for 68K
- assembler in your extensions:
-
- pascal void SetA0( void* ) = { 0x205F };
- pascal void SetA1( void* ) = { 0x225F };
- void * GetA0( void ) = { 0x2008 };
- void * GetA7( void ) = { 0x200F };
-
-
- ---------------------------------------------------------------------
- --
-
- Trap Patches:
-
- --
- [17] What exactly is a trap patch?
-
- A trap patch is a method for changing the functionality of a trap.
- The addresses of all the traps are maintained in the two trap
- dispatch tables. By using the routine NSetTrapAddress and friends
- you can change the address of a particular trap to code that you
- provide. When this is done at INIT time, all calls to the patched
- trap from all applications will go to your code, which in most cases
- will do something and then call through the existing trap in the
- ROMs. Be warned that the Finder patches some traps when it starts up
- in a way that prevents previously-installed patches from executing.
-
- I recommend that you read the descriptions of the trap dispatch
- mechanism in the 'Using Assembly Language' chapters in IM I and IV
- and also in the 'Trap Manager' chapter in NIM Operating System
- Utilities.
-
- Traps come in several types based on their parameter passing
- conventions. Most toolbox traps use pascal calling conventions,
- which means that all parameters are passed on the stack and the
- return value, if any, is placed on the stack.
-
- Some traps use register-based calling conventions. In these traps
- the parameters are passed in registers and the return value is
- returned in a register, usually D0. For example all the Memory
- Manager traps are register-based and the File Manager traps are also
- register-based.
-
- Some traps are selector-based. There are only so many spots in the
- trap-dispatch tables. In order to preserve space in these tables
- selector-based traps have been developed. In these traps a single
- trap serves as the front end for a number of system routines. The
- parameters of these traps are passed in the usual manner, either on
- the stack or in registers, and a selector is also passed, usually in
- a register. When the trap is called it checks the selector and then
- dispatches to the appropriate routine. Patching each of these types
- of traps involves different mechanisms. We'll look at samples of
- each one.
-
- Patching traps on the PowerMac is a bit different than on the 68K
- Macs. Most of the discussion here is aimed at patching traps on the
- 68K Macs. Hopefully I'll learn some more about this subject soon and
- there will be some better info here on patching traps on the
- PowerMac.
-
- --
- [18] What's the difference between a head patch and a tail patch?
-
- It is most common to add some functionality to a trap when patching
- it rather than just replacing the existing trap. For instance I've
- written an extension that speaks the text in alerts by using the
- Speech Manager. This works by patching Alert and friends. When
- Alert is called the patch gets the text that appears in the alert and
- passes it to the Speech Manager. The patch then calls the existing
- Alert trap that is in the ROMs. A patch that works in this way is
- called a head patch; it does its business and then it calls the
- previous trap.
-
- A tail patch is a bit different. A virus-checking program might want
- to patch GetResource and then examine the resource that was read in
- to see if it contains a virus. In order to do this the patch must
- first call the existing trap and then do its processing, and finally
- return to the application. This is a tail patch because some
- processing occurs after the existing trap is called. In order for a
- patch to be a head patch you must use a jmp instruction to jump to
- the previous trap. If you use a jsr or a C function pointer to jump
- to the previous trap you have a tail patch.
-
- The reason that this head and tail patch business has been so
- important in the past is because of an Apple invention called the
- come-from patch. Patches were invented, of course, so that Apple
- could fix bugs in the ROMs and could update the routines in the ROMs
- with software. This is why you can run system 7 on a Mac Plus, whose
- ROMs are obviously missing most of the additions made to the toolbox
- in recent years.
-
- Certain ROM routines are particularly large so it is inconvenient to
- patch them if they have bugs in them. To get around this problem the
- Apple programmers searched for smaller routines that are called from
- the large buggy routines and placed patches in the smaller routines.
- These patches check the return address on the stack. If it is the
- address of the buggy routine then a fix is applied. If not then they
- just go on as usual. The patches to these smaller routines are known
- as come-from patches. If you tail patch one of these and then call
- the existing come-from patch, the return address on the stack will be
- in your patch and not the buggy routine that called you. In this
- case the come-from patch will not apply its fix and your system will
- crash.
-
- The good news is that as of system 7 it is safe to apply tail
- patches. The come-from patches still exist in system software, but
- NGetTrapAddress has been modified to return an address that is safe
- to use when applying tail-patches. However, if you wish your
- extension to run in System 6 then tail-patches are not allowed. This
- is documented in the Trap Manager chapter in NIM: OS Utilities.
-
- If you absolutely positively need a tail patch in System 6 then the
- following logic may apply: (Just don't tell anyone that I told you
- this :-) Apple is not releasing any new versions of System 6 so no
- new come-from patches will be forthcoming for System 6. If you do
- careful testing of the traps you wish to tail-patch you will probably
- be OK. In general traps not called from the ROMs, like Alert and
- MenuKey, will not contain come-from patches.
-
- One final thing: In system 7 it is safe to apply tail-patches to all
- traps except FrontWindow.
-
- --
-
- --------------------comp.sys.mac.programmer.info---------------------
- comp.sys.mac.programmer.info is primarily for distributing FAQs,
- tutorials, news, and similar documents related to programming the
- Macintosh. To post, email csmp_info@xplain.com
- -----------------------about MacTech Magazine----------------------
- PO Box 250055, Los Angeles, CA 90025, 310-575-4343, Fax:310-575-0925
- For more info, anonymous ftp to ftp.netcom.com and cd to /pub/xplain
- or email to the following @xplain.com : custservice, editorial,
- adsales, marketing, accounting, pressreleases, progchallenge,
- publisher, info
-